package com.sijobe.installer;

// TODO: Add delete tags
// TODO: Add script tags

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * Handles the manifest file operations and variables, the class basically 
 * provides accessors for each of the elements within the XML structured 
 * manifest file.
 *
 * Example XML formatted manifest:
 *
 * <install version="1.2">
 *   <project name="Single Player Commands">
 *     <version>3.2.2</version>
 *     <datetime>2011-05-01 17:00:00</datetime>
 *     <banner>banner.png</banner>
 *     <description>
 *        The mod adds a command line interface into Single player 
 *        which allows use of well over one hundred commands and countless new 
 *        features. 
 *     </description>
 *     <manifest>
 *       <file source="*.class" destination="/bin/Minecraft.jar" required="true" description="" />
 *       <file source="*.jar" destination="/bin/" required="true" description="" />
 *       <script type="java" source="ScriptTest.class" class="ScriptTest" dependencies="WorldEdit.jar;Minecraft.jar" />
 *     </manifest>
 *     <minecraft>
 *        <supportedversion>1.2.5</supportedversion>
 *        <supportedversion>1.2.4</supportedversion>
 *     </minecraft>
 *     <footer>
 *        <links>
 *           <link name="readme" position="1">readme.txt</link>
 *           <link name="terms" position="2">terms.txt</link>
 *           <link name="website" position="3">http://bit.ly/spcmod</link>
 *        </links>
 *     </footer>
 *   </project>
 * </install>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author simo_415
 */
public class ManifestHandler {

   public static final String ATTRIBUTE_NAMES[] = new String[]{
      "version","datetime","banner","description"
   };
   private File manifest;
   private Element base;
   private Map projects;
   private Map attributes;
   private Map files;

   public ManifestHandler(File manifest) {
      this.manifest = manifest;
      this.attributes = new HashMap();
      this.files = new HashMap();
      try {
         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
         DocumentBuilder db = dbf.newDocumentBuilder();
         Document d = db.parse(this.manifest);
         base = d.getDocumentElement();
         if (base.getAttribute("version").compareTo("1.2") != 0) {
            throw new Exception("Manifest file is the wrong version for this reader. A different version of manifest detected");
         }
      } catch (Exception e) {
         throw new RuntimeException(e);
      }
   }

   /**
    * Gets the element map of projects contained within the manifest
    * 
    * @return The map containing Element objects
    */
   private Map getProjects() {
      if (projects == null) {
         projects = new HashMap();
         NodeList nl = base.getElementsByTagName("project");
         if(nl != null) {
            for(int i = 0; i < nl.getLength(); i++) {
               Element e = (Element)nl.item(i);
               if (e != null) {
                  projects.put(e.getAttribute("name"), e);
               }
            }
         }
      }
      return projects;
   }

   /**
    * Gets a String array of the projects defined within the manifest 
    * 
    * @return An array of all the projects in the manifest
    */
   public String[] getProjectNames() {
      String names[] = new String[getProjects().size()];
      Iterator i = getProjects().keySet().iterator();
      int j = 0;
      while (i.hasNext()) {
         names[j++] = (String)i.next();
      }
      return names;
   }

   /**
    * Gets the specified project by name
    * 
    * @param name - The name of the project
    * @return An Element node representing the project
    */
   private Element getProject(String name) {
      return (Element)getProjects().get(name);
   }

   /**
    * Gets the associated project attributes
    * 
    * @param project - The name of the project
    * @return A Map containing attribute>value pairs
    */
   public Map getProjectAttributes(String project) {
      if (!this.attributes.containsKey(project)) {
         Element e = getProject(project);
         if (e == null) {
            return null;
         }
         HashMap attributes = new HashMap();
         for (int i = 0; i < ATTRIBUTE_NAMES.length; i++) {
            NodeList n = e.getElementsByTagName(ATTRIBUTE_NAMES[i]);
            if(n != null && n.getLength() == 1) {
               attributes.put(ATTRIBUTE_NAMES[i],((Element)n.item(0)).getTextContent());
            } else {
               attributes.put(ATTRIBUTE_NAMES[i],"");
            }
         }
         this.attributes.put(project,attributes);
      }
      return (Map)this.attributes.get(project);
   }

   /**
    * Gets the specified project's manifest file
    * 
    * @param project - The name of the project
    * @return A list containing ManifestFile objects 
    */
   public List getProjectManifest(String project) {
      if (!files.containsKey(project)) {
         ArrayList manifest = new ArrayList();
         Element e = getProject(project);
         if (e == null) {
            return null;
         }
         NodeList rawmanifest = e.getElementsByTagName("manifest");
         for (int i = 0; i < rawmanifest.getLength(); i++) { // Loops over all manifest tags (should only be one)
            NodeList files = ((Element)rawmanifest.item(i)).getElementsByTagName("file");
            for (int j = 0; j < files.getLength(); j++) { // Loops over all file tags
               Element file = ((Element)files.item(j));
               if (file == null) {
                  continue;
               }
               String source = file.getAttribute("source");
               String destination = file.getAttribute("destination");
               String description = file.getAttribute("description");
               boolean required = true;
               try {
                  required = Boolean.parseBoolean(file.getAttribute("required"));
               } catch (Exception exc) {}
               manifest.add(new ManifestFile(source,destination,description,required));
            }
            NodeList scripts = ((Element)rawmanifest.item(i)).getElementsByTagName("script");
            for (int j = 0; j < scripts.getLength(); j++) { // Loops over all script tags
               Element script = ((Element)scripts.item(j));
               if (script == null) {
                  continue;
               }
               String source = script.getAttribute("source");
               String type = script.getAttribute("type");
               String classname = script.getAttribute("class");
               String dependency = script.getAttribute("dependencies");
               String dependencies[] = null;
               if (dependency != null) {
                  dependencies = dependency.split(";");
               }
               manifest.add(new ManifestScript(source,type,classname,dependencies));
            }
         }
         files.put(project, manifest);
      }
      return (List)files.get(project);
   }

   /**
    * Used to retrieve the list of supported Minecraft versions
    * 
    * <minecraft>
    *        <supportedversion>1.2.5</supportedversion>
    *        <supportedversion>1.2.4</supportedversion>
    * </minecraft>
    * 
    * @param project - The project name
    * @return An array of all the supported versions
    */
   public String[] getMinecraftVersions(String project) {
      String versions[] = null;
      Element e = getProject(project);
      if (e == null) {
         return null;
      }
      NodeList minecraft = e.getElementsByTagName("minecraft");
      for (int i = 0; i < minecraft.getLength(); i++) { // Loops over all manifest tags (should only be one)
         NodeList version = ((Element)minecraft.item(i)).getElementsByTagName("supportedversion");
         versions = new String[version.getLength()];
         for (int j = 0; j < version.getLength(); j++) { // Loops over all version tags
            Element v = ((Element)version.item(j));
            if (v == null) {
               continue;
            }
            versions[j] = v.getTextContent();
         }
      }
      return versions;
   }

   /**
    * TODO: Cache the results
    * 
    * Returns a list of FooterLink objects that contain the links to appear on
    * the GUI footer. This allows custom links to be added to the GUI with 
    * minimal effort and allows things to be removed as well. This replaces the 
    * old tags that specified the readme, terms and website.
    * 
    * The footer XML is specified as below:
    * 
    * <footer>
    *   <links>
    *     <link name="readme" position="0">readme.txt</link>
    *     <link name="terms" position="1">terms.txt</link>
    *     <link name="website" position="2">http://bit.ly/spcmod</link>
    *     <link name="support" position="3">http://bit.ly/spcmod</link>
    *   </links>
    * </footer>
    * 
    * Note: Maximum of 10 links can be specified
    * 
    * @param project - The name of the project to use
    * @return List<FooterLink> with all footer link details
    */
   public List getFooterLinks(String project) {
      int maxlinks = 10;
      ArrayList list = new ArrayList();
      for (int i = 0; i < maxlinks; i++) {
         list.add(null);
      }
      list.clear();
      Element e = getProject(project);
      if (e == null) {
         return null;
      }
      NodeList minecraft = e.getElementsByTagName("footer");
      for (int i = 0; i < minecraft.getLength(); i++) { // Loops over all footer tags (should only be one)
         NodeList links = ((Element)minecraft.item(i)).getElementsByTagName("links");
         for (int j = 0; j < links.getLength() && j < 1; j++) { // Loops over 1 links tag (should only be one)
            NodeList link = ((Element)minecraft.item(i)).getElementsByTagName("link");
            for (int k = 0; k < link.getLength() && k < maxlinks; k++) { // Loops over link tags
               Element v = ((Element)link.item(k));
               if (v == null) {
                  continue;
               }
               try {
                  String name = v.getAttribute("name");
                  int position = Integer.parseInt(v.getAttribute("position"));
                  String value = v.getTextContent();
                  try {
                     list.add(position,new FooterLink(name,position,value));
                  } catch (Exception ex) {
                     list.add(new FooterLink(name,position,value));
                  }
               } catch (Exception ex) {
                  ex.printStackTrace();
               }
            }
         }
      }
      return list;
   }

   /**
    * Convenience method to get the version of the specified project
    * 
    * @param project - The project name
    * @return The version value
    */
   public String getVersion(String project) {
      return (String)getProjectAttributes(project).get("version");
   }

   /**
    * Convenience method to get the datetime of the specified project
    * 
    * @param project - The project name
    * @return The datetime value
    */
   public String getDateTime(String project) {
      return (String)getProjectAttributes(project).get("datetime");
   }

   /**
    * Convenience method to get the website of the specified project
    * 
    * @param project - The project name
    * @return The website value
    * @deprecated Replaced with the getFooterLinks method
    */
   public String getWebsite(String project) {
      return (String)getProjectAttributes(project).get("website");
   }

   /**
    * Convenience method to get the readme of the specified project
    * 
    * @param project - The project name
    * @return The readme value
    * @deprecated Replaced with the getFooterLinks method
    */
   public String getReadme(String project) {
      return (String)getProjectAttributes(project).get("readme");
   }

   /**
    * Convenience method to get the terms of the specified project
    * 
    * @param project - The project name
    * @return The terms value
    * @deprecated Replaced with the getFooterLinks method
    */
   public String getTerms(String project) {
      return (String)getProjectAttributes(project).get("terms");
   }

   /**
    * Convenience method to get the banner of the specified project
    * 
    * @param project - The project name
    * @return The banner value
    */
   public String getBanner(String project) {
      return (String)getProjectAttributes(project).get("banner");
   }

   /**
    * Convenience method to get the description of the specified project
    * 
    * @param project - The project name
    * @return The description value
    */
   public String getDescription(String project) {
      return (String)getProjectAttributes(project).get("description");
   }

   /**
    * Convenience method to get the version of Minecraft of the specified project
    * 
    * @param project - The project name
    * @return The Minecraft version value
    */
   public String getMinecraftVersion(String project) {
      return (String)getProjectAttributes(project).get("minecraftversion");
   }
}

class ManifestFile {
   private String source;
   private String destination;
   private String description;
   private boolean required;

   ManifestFile(String source, String destination, String description, boolean required) {
      this.source = source;
      this.destination = destination;
      this.description = description;
      this.required = required;
   }

   public String getSource() {
      return source;
   }

   public String getDestination() {
      return destination;
   }

   public String getDescription() {
      return description;
   }

   public boolean getRequired() {
      return required;
   }
}

class ManifestScript {
   private String source;
   private String type;
   private String classname;
   private String dependencies[];

   ManifestScript(String source, String type, String classname, String dependencies[]) {
      this.source = source;
      this.type = type;
      this.classname = classname;
   }

   public String getSource() {
      return source;
   }

   public String getType() {
      return type;
   }

   public String getClassname() {
      return classname;
   }

   public String[] getDependencies() {
      return dependencies;
   }
}

class FooterLink {
   private String name;
   private int position;
   private String value;

   public FooterLink(String name, int position, String value) {
      this.name = name;
      this.position = position;
      this.value = value;
   }

   /**
    * @return the name
    */
   public String getName() {
      return name;
   }

   /**
    * @return the position
    */
   public int getPosition() {
      return position;
   }

   /**
    * @return the value
    */
   public String getValue() {
      return value;
   }  
}
